"""
@-*- coding: utf-8 -*-
@ python：python 3.8
@ 创建人员：ylx
@ 创建时间：2025/2/27 下午5:43
@ 内容：python反射机制学习
"""
"""
python的反射机制，核心就是利用字符串去已存在的模块中找到指定的属性或方法
找到方法后自动执行，既基于字符串的事件驱动
在python的自醒或者反射机制中，我们经常会用到下面4个函数
hasattr 判断对象是否有指定名字的属性或方法，返回值类型为bool类型
setattr 给指定的对象添加属性和属性值
getattr(object, name) 获取对象指定名称的属性或方法，返回值为str类型 object是我们要操作的对象，name的类型是字符串
delattr 删除对象指定名称的属性或者方法值，无返回值

什么是反射
在很多动态语言中都有反射机制(reflection mechanism)，python也不例外。
为什么强调动态语言呢？ 因为动态语言很多允许我们动态的新增、修改、删除类（或者对象）的方法与属性。 这无疑是一个非常强大的功能！

class Person:
    type = "mammal"
    
    def __init__(self, name):
        self.name = name
        
    def say_hi(self):
        print('Hello, my name is', self.name)
        
    @staticmethod
    def feed():
        print("Three times per day.")
        
        
    @classmethod
    def sleep(cls):
        print("8 hours!")

p = Person('phoenix')  # 实例化一个Person对象，实例化后的对象名称为p
p.say_hi()   # 对实例化后的对象调用实例方法
print(f"{p.name = }")  # 对实例化后的对象调用实例属性,这个是f-string的特殊用法  这样的话输出就是这样的一个形式，没有=就是直接展示值


现在我遇到一个问题: 我不知道某一个类(假设就是上面例子中的Person)是不是有某一个方法（假设就是上面例子中的say_hi()），我应该怎么调用呢?
你可能觉得这有点抬杠了，会觉得：
1. 类不是自己写的吗，即使不是自己写的那也应该是继承的，应该知道它有哪些方法啊
2. 这个类我都已经实例化了，怎么还不知道有没有这个方法呢？

其实上面的疑惑主要的原因是没理解动态语言的概念， 即然是动态的，那么方法、属性就有可能发生变化（也就有能增加或者被删除）
反射机制 就是用来解决这类型问题的，下面我们详细说明。

p = Person('phoenix')
func_say = getattr(p, 'say_hi')
func_say()
print(f"{getattr(p, 'name') = }")
输出结果
Hello, my name is phoenix
getattr(p, 'name') = 'phoenix'


p = Person('phoenix')
setattr(p, 'say_bye', Person.feed)
setattr(p, 'age', 18)
print(f"{p.say_bye() = }")
#当你执行上面这个print代码时
# 调用 p.say_bye()：
# p.say_bye 是通过 setattr 动态绑定的，它指向 Person.feed。
# 因此，p.say_bye() 实际上调用了 Person.feed()。
# 执行 Person.feed()：
# Person.feed 方法内部调用了 print("Three times per day.")。
# 这行代码会立即将字符串 "Three times per day." 输出到控制台。
# Person.feed 方法没有显式返回任何值，因此它的返回值默认是 None。
# f-string 插入了 p.say_bye() 的返回值（即 None），并显示为 p.say_bye() = None。
# 将 Person.feed 方法动态绑定到了实例 p 的 say_bye 属性上怎么理解，其次say_bye是属性
# 为什么还能这样使用p.say_bye() ，不应该没有括号吗

解答-
在 Python 中，setattr 函数允许我们在运行时为对象动态添加或修改属性。例如：
class Person:
    pass

p = Person()
setattr(p, 'name', 'Alice')
print(p.name)  # 输出: Alice
这里我们通过 setattr 为实例 p 动态添加了一个名为 name 的属性，并赋值为 'Alice'
类似在我们的这段代码将类 Person 的静态方法 feed 动态绑定到了实例 p 的 say_bye 属性上。
换句话说，p.say_bye 现在指向了 Person.feed 方法。
绑定的是什么？
Person.feed 是一个静态方法（@staticmethod），它本质上是一个普通的函数，只是定义在类的命名空间中。
当你执行 setattr(p, 'say_bye', Person.feed) 时，p.say_bye 被设置为指向这个函数
在 Python 中，属性和方法的区别主要在于是否可以调用：
普通属性：直接访问，不能调用。例如：p.name  # 访问属性
方法：可以通过括号调用。例如p.some_method()  # 调用方法
虽然 say_bye 是通过 setattr 动态添加的属性，但它指向的是一个可调用的对象（即函数）。具体来说：
Person.feed 是一个静态方法，本质上是一个函数。
在 Python 中，函数是“可调用对象”（callable objects），因此可以通过括号调用。
当你执行 p.say_bye() 时：

Python 解释器会查找 p.say_bye 的值。
发现 p.say_bye 是一个函数（因为它是 Person.feed）。
因此，允许你像调用普通方法一样调用它。
class Person:
    @staticmethod
    def feed():
        print("Three times per day.")

p = Person()

# 动态绑定 say_bye 属性
setattr(p, 'say_bye', Person.feed)

# 查看 say_bye 的类型
print(type(p.say_bye))  # 输出: <class 'function'>

# 调用 say_bye
p.say_bye()  # 输出: Three times per day.

总结：为什么 p.say_bye() 可以调用？
动态绑定的本质：p.say_bye 是通过 setattr 动态添加的属性，但它指向的是一个函数（Person.feed）。
函数的可调用性：在 Python 中，函数是“可调用对象”，因此可以通过括号调用。
动态绑定后的行为：尽管 say_bye 是动态添加的属性，但由于它指向的是一个函数，因此可以像普通方法一样调用。

如果你将 say_bye 设置为一个非函数对象，例如字符串或整数，那么调用 p.say_bye() 会导致错误
class Person:
    pass

p = Person()
setattr(p, 'say_bye', "Hello")  # 将 say_bye 设置为字符串

p.say_bye()  # TypeError: 'str' object is not callable
p.say_bye 是一个字符串，而不是可调用对象

动态绑定 是通过 setattr 在运行时为对象添加或修改属性的过程。
如果绑定的属性指向一个函数（如静态方法 Person.feed），那么该属性就可以像方法一样调用。
Python 的灵活性使得我们可以动态地操作对象的属性和方法，这是其动态特性的体现之一。

print(f"{getattr(p, 'age') = }")


执行结果
Three times per day.
p.say_bye() = None
getattr(p, 'age') = 18



p = Person('phoenix')
print(f"{hasattr(Person, 'type') = }, {hasattr(p, 'type') = }")
print(f"{hasattr(Person, 'name') = }, {hasattr(p, 'name') = }")
print(f"{hasattr(Person, 'say_hi') = }, {hasattr(p, 'say_hi') = }")
print(f"{hasattr(Person, 'sleep') = }, {hasattr(p, 'sleep') = }")
print(f"{hasattr(Person, 'feed') = }, {hasattr(p, 'feed') = }")
执行结果
hasattr(Person, 'type') = True, hasattr(p, 'type') = True
hasattr(Person, 'name') = False, hasattr(p, 'name') = True
hasattr(Person, 'say_hi') = True, hasattr(p, 'say_hi') = True
hasattr(Person, 'sleep') = True, hasattr(p, 'sleep') = True
hasattr(Person, 'feed') = True, hasattr(p, 'feed') = True


p = Person('phoenix')
setattr(p, 'age', 18)
print(f"before delete : {hasattr(p, 'age') = }")
delattr(p, 'age')
print(f"after delete  : {hasattr(p, 'age') = }")
执行结果
before delete : hasattr(p, 'age') = True
after delete  : hasattr(p, 'age') = False

反射机制实际的使用场景
如下
def f1():
  print("f1是这个函数的名字！")
  
s = "f1"
print("%s是个字符串"%s)
前者是函数f1的函数名，后者只是一个叫”f1“的字符串
两者是不同的事物。我们可以用f1()的方式调用函数f1，但我们不能用"f1"()的方式调用函数。说白了就是，不能通过字符串来调用名字看起来相同的函数
web实例，我们现在有一个需求，根据用户的输入，来跳转到不同的页面
那我们首先有一个commons模块，用于展示不同的页面
def login():
  print("这是一个登陆页面！")
 
def logout():
  print("这是一个退出页面！")
 
def home():
  print("这是网站主页面！")
  
我们还有一个visit模块，用于接收用户的输入，从而展示对应的页面
import commons
 
def run():
  inp = input("请输入您想访问页面的url： ").strip()
  if inp == "login":
    commons.login()
  elif inp == "logout":
    commons.logout()
  elif inp == "home":
    commons.home()
  else:
    print("404")
 
if __name__ == '__main__':
  run()
我们运行visit.py，输入：home，页面结果如下：
请输入您想访问页面的url： home
这是网站主页面！
这就实现了一个简单的WEB路由功能，根据不同的url，执行不同的函数，获得不同的页面。
然而，让我们考虑一个问题，如果commons模块里有成百上千个函数呢(这非常正常)?
难道你在visit模块里写上成百上千个elif?显然这是不可能的！那么怎么破？
从而我们引入了反射机制，来解决这个问题
仔细观察visit中的代码，我们会发现用户输入的url字符串和相应调用的函数名好像！
如果能用这个字符串直接调用函数就好了！但是，前面我们已经说了字符串是不能用来调用函数的。
为了解决这个问题，
python为我们提供一个强大的内置函数：getattr!我们将前面的visit修改一下，代码如下：
import commons
 
def run():
  inp = input("请输入您想访问页面的url： ").strip()
  func = getattr(commons,inp)
  func()
  
if __name__ == '__main__':
  run()

首先说明一下getattr函数的使用方法：它接收2个参数，前面的是一个对象或者模块
后面的是一个字符串，注意了！是个字符串！
例子中，用户输入储存在inp中，这个inp就是个字符串
getattr函数让程序去commons这个模块里，寻找一个叫inp的成员（是叫，不是等于）
这个过程就相当于我们把一个字符串变成一个函数名的过程
然后，把获得的结果赋值给func这个变量，实际上func就指向了commons里的某个函数
最后通过调用func函数，实现对commons里函数的调用。
这完全就是一个动态访问的过程，一切都不写死，全部根据用户输入来变化。
这就是python的反射，它的核心本质其实就是利用字符串的形式去对象（模块）中操作（查找/获取/删除/添加）成员，一种基于字符串的事件驱动！
如果用户输入错误的，既我们commons中不存在的成员，我们可以这样优化下面的代码
import commons
  
def run():
  inp = input("请输入您想访问页面的url： ").strip()
  if hasattr(commons,inp):
    func = getattr(commons,inp)
    func()
  else:
    print("404")
  
if __name__ == '__main__':
  run()
  
  
python的四个重要内置函数：getattr、hasattr、delattr和setattr较为全面的实现了基于字符串的反射机制。他们都是对内存内的模块进行操作，并不会对源文件进行修改。


动态导入模块

上面的例子是在某个特定的目录结构下才能正常实现的，也就是commons和visit模块在同一目录下，并且所有的页面处理函数都在commons模块内。如下图：
但在现实使用环境中，页面处理函数往往被分类放置在不同目录的不同模块中
难道我们要在visit模块里写上一大堆的import 语句逐个导入account、manage、commons模块吗？要是有1000个这种模块呢？
刚才我们分析完了基于字符串的反射，实现了动态的函数调用功能，我们不禁会想那么能不能动态导入模块呢？这完全是可以的！
python提供了一个特殊的方法：__import__(字符串参数)。通过它，我们就可以实现类似的反射功能。__import__()方法会根据参数，动态的导入同名的模块。
我们再修改一下上面的visit模块的代码。
def run():
  inp = input("请输入您想访问页面的url： ").strip()
  modules, func = inp.split("/")
  obj = __import__(modules)
  if hasattr(obj, func):
    func = getattr(obj, func)
    func()
  else:
    print("404")
  
if __name__ == '__main__':
  run()

我们来分析一下上面的代码：
　　首先，我们并没有定义任何一行import语句；
　　其次，用户的输入inp被要求为类似“commons/home”这种格式，其实也就是模拟web框架里的url地址，斜杠左边指向模块名，右边指向模块中的成员名。
　　然后，modules,func = inp.split("/")处理了用户输入，使我们获得的2个字符串，并分别保存在modules和func变量里。
　　接下来，最关键的是obj = __import__(modules)这一行，它让程序去导入了modules这个变量保存的字符串同名的模块，并将它赋值给obj变量。
　　最后的调用中，getattr去modules模块中调用func成员的含义和以前是一样的。
　　总结：通过__import__函数，我们实现了基于字符串的动态的模块导入。


优化，如果模块都在一个包下，这个包指比如说都在一个目录下，目录下有对应的模块，那我们
def run():
  inp = input("请输入您想访问页面的url： ").strip()
  modules, func = inp.split("/")
  obj = __import__("lib." + modules, fromlist=True) # 注意fromlist参数，如果不加为什么呢？因为对于lib.xxx.xxx.xxx这一类的模块导入路径，
                                                    #__import__默认只会导入最开头的圆点左边的目录，也就是lib我们可以做个测试
  if hasattr(obj, func):
    func = getattr(obj, func)
    func()
  else:
    print("404")
  
if __name__ == '__main__':
  run()
  
至此，动态导入模块的问题基本都解决了，只剩下最后一个，那就是万一用户输入错误的模块名呢？比如用户输入了somemodules/find，由于实际上不存在somemodules这个模块，必然会报错！
那有没有类似上面hasattr内置函数这么个功能呢？答案是没有！碰到这种，你只能通过异常处理来解决。
https://www.cnblogs.com/yanglang/p/8005383.html


python的内省是指程序在运行时检查对象的能力
Python的反射是指程序在运行时修改对象的能力
内省是指程序在运行时了解对象的类型、属性和方法的能力。
常见的内省工具
type()函数 函数用于获取对象的类型。它告诉你一个对象是一个整数、字符串、列表还是其他类型。对于检查对象的类型非常有用。
dir()函数用于获取对象的属性和方法列表。它可以帮你了解对象的可用功能。在探索模块或类时非常有用。
hasattr()、getattr()和setattr()函数，用于检查、获取和设置对象的属性。它们能够在运行时操作对象的属性。

反射是指程序在运行时修改对象的能力。
Python的反射机制允许您动态创建类、调用方法、获取和设置属性，以及修改对象的行为。

python的反射机制它允许程序在运行时动态地检查、调用和修改对象的属性和方法。
在 Python 中，反射功能主要通过内置函数 getattr()、setattr()、hasattr() 和 delattr() 来实现。
这些函数让你可以动态地访问和修改对象的属性，以及调用对象的方法，甚至可以在不知道具体函数名称的情况下，
通过字符串形式动态地调用函数
在 Python 中，反射机制通常用于以下几种情况：
动态获取和设置对象的属性。
动态调用对象的方法。
动态导入模块并调用模块中的函数。
对于反射的理解，可以把它看作是一种动态的“自省”（introspection）和操作机制
它使得程序可以检查和操作自身的结构，而不需要在编译时写死所有的行为。
反射的一个重要应用是动态地访问和修改对象的属性。例如，当我们需要从外部数据（如配置文件或用户输入）来决定访问哪个属性时，就可以通过反射来实现


"""